package com.think.cloud.thinkshop.mall.service.integral.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.think.cloud.thinkshop.mall.domain.integral.IntegralBill;
import com.think.cloud.thinkshop.mall.domain.integral.IntegralDetail;
import com.think.cloud.thinkshop.mall.enums.integral.IntegralBillTypeEnum;
import com.think.cloud.thinkshop.mall.enums.integral.IntegralCleanTypeEnum;
import com.think.cloud.thinkshop.mall.enums.integral.IntegralDetailStatusEnum;
import com.think.cloud.thinkshop.mall.mapper.integral.IntegralDetailMapper;
import com.think.cloud.thinkshop.mall.service.integral.IIntegralBillService;
import com.think.cloud.thinkshop.mall.service.integral.IIntegralDetailService;
import com.think.common.core.exception.enums.ErrorCode;
import com.think.common.core.exception.util.ServiceExceptionUtil;
import lombok.SneakyThrows;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.text.MessageFormat;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * 积分详情Service业务层处理
 *
 * @author moxiangrong
 * @date 2024-07-17
 */
@Service
public class IntegralDetailServiceImpl implements IIntegralDetailService {
    private static final String REDUCE_INTEGRAL_KEY = "integral:reduce:user:{0}";
    @Autowired
    private IntegralDetailMapper integralDetailMapper;
    @Autowired
    private IIntegralBillService integralBillService;
    @Autowired
    private RedissonClient redissonClient;


    /**
     * 查询积分详情
     *
     * @param id 积分详情主键
     * @return 积分详情
     */
    @Override
    public IntegralDetail selectIntegralDetailById(Long id) {
        return integralDetailMapper.selectById(id);
    }

    /**
     * 查询积分详情列表
     *
     * @param integralDetail 积分详情
     * @return 积分详情
     */
    @Override
    public List<IntegralDetail> selectIntegralDetailList(IntegralDetail integralDetail) {
        return integralDetailMapper.selectList(new LambdaQueryWrapper<>());
    }

    /**
     * 新增积分详情
     * @return 结果
     */
    @Override
    @Transactional
    public int insertIntegralDetail(Long userId,Integer integral,IntegralBillTypeEnum billTypeEnum) {
        IntegralDetail integralDetail = IntegralDetail.builder()
                .userId(userId)
                .totalIntegral(integral)
                .remainIntegral(integral)
                .build();
        int insert = integralDetailMapper.insert(integralDetail);
        if (insert > 0) {
            this.insertBill(
                    userId,
                    integralDetail.getId().toString(),
                    integral,
                    billTypeEnum
            );
        }
        return insert;
    }

    /**
     * 添加积分流水记录
     */
    public void insertBill(Long userId, String detail, int integral, IntegralBillTypeEnum type) {
        String integralStr = null;
        if (integral > 0) {
            integralStr = "+" + integral;
        }
        if (integral < 0) {
            integralStr = String.valueOf(integral);
        }
        if (integral == 0) {
            integralStr = "0";
        }
        IntegralBill bill = IntegralBill.builder()
                .userId(userId)
                .detail(detail)
                .integral(integralStr)
                .billType(type.getCode())
                .userIntegral(getUserCurrentIntegral(userId))
                .build();
        integralBillService.insertIntegralBill(bill);
    }


    /**
     * 修改积分详情
     *
     * @param integralDetail 积分详情
     * @return 结果
     */
    @Override
    public int updateIntegralDetailById(IntegralDetail integralDetail) {
        return integralDetailMapper.updateById(integralDetail);
    }

    /**
     * 批量删除积分详情信息
     *
     * @param ids 主键集合
     * @return 结果
     */
    @Override
    public int batchDelete(List<Long> ids) {
        return integralDetailMapper.deleteBatchIds(ids);
    }

    @Override
    public int getUserCurrentIntegral(Long userId) {
        List<IntegralDetail> integralDetails = this.selectIntegralDetails(
                this.getUserIntegralWrappers(userId, null)
        );
        if (integralDetails == null || integralDetails.isEmpty()) {
            return 0;
        }
        return integralDetails.stream().mapToInt(IntegralDetail::getRemainIntegral).sum();
    }

    //查询积分详情
    private List<IntegralDetail> selectIntegralDetails(LambdaQueryWrapper<IntegralDetail> query) {
        return integralDetailMapper.selectList(query);
    }

    /**
     * 扣除积分
     */
    @SneakyThrows
    @Override
    public Boolean reduceUserIntegral(Long userId, int num) {
        if (num == 0) {
            return Boolean.TRUE;
        }
        if (num < 0) {
            return Boolean.FALSE;
        }
        Boolean result = Boolean.FALSE;
        String lockKey = MessageFormat.format(REDUCE_INTEGRAL_KEY, userId);
        RLock lock = redissonClient.getLock(lockKey);
        boolean getLock = lock.tryLock(30, 30, TimeUnit.SECONDS);
        if (getLock) {
            try {
                int userCurrentIntegral = getUserCurrentIntegral(userId);
                //积分不足
                if (userCurrentIntegral < num) {
                    result = Boolean.FALSE;
                } else {
                    this.reduceIntegral(userId, num);
                    result = Boolean.TRUE;
                }
            } catch (Exception e) {
                throw ServiceExceptionUtil.exception(ErrorCode.SYSTEM_BUSY);
            } finally {
                lock.unlock();
            }
        }
        return result;
    }

    private void reduceIntegral(Long userId, int reduceNum) {
        List<IntegralDetail> integralDetails = this.selectIntegralDetails(
                this.getUserIntegralWrappers(userId, 0).orderByAsc(IntegralDetail::getCreateTime)
        );
        //详情中的可用积分和
        int total = 0;
        //记录执行的相关id
        List<String> ids = new ArrayList<>();
        //看看需要使用多少详情的积分
        for (IntegralDetail integralDetail : integralDetails) {
            total += integralDetail.getRemainIntegral();
            Long id = integralDetail.getId();
            ids.add(id.toString());
            if (total >= reduceNum) {
                //刚好使用完记录中的积分,或这个有剩余
                int remain = total - reduceNum;
                IntegralDetail update = IntegralDetail.builder()
                        .id(id)
                        .remainIntegral(remain)
                        .status(remain == 0 ?
                                IntegralDetailStatusEnum.FULL_USE.getCode() :
                                IntegralDetailStatusEnum.NORMAL.getCode())
                        .build();
                this.updateIntegralDetailById(update);
                break;
            }
            IntegralDetail update = IntegralDetail.builder()
                    .id(id)
                    .remainIntegral(0)
                    .status(IntegralDetailStatusEnum.FULL_USE.getCode())
                    .build();
            this.updateIntegralDetailById(update);
        }
        //流水记录
        insertBill(userId, String.join(",", ids), -reduceNum, IntegralBillTypeEnum.USE);
    }

    /**
     * 基本查询参数
     *
     * @param userId
     * @return
     */
    private LambdaQueryWrapper<IntegralDetail> getUserIntegralWrappers(Long userId, Integer remain) {
        return Wrappers.<IntegralDetail>lambdaQuery()
                .eq(userId != null, IntegralDetail::getUserId, userId)
                .eq(IntegralDetail::getStatus, IntegralDetailStatusEnum.NORMAL.getCode())
                .ge(remain != null, IntegralDetail::getRemainIntegral, 0)
                ;
    }

    @Override
    public void cleanIntegral(Integer clearType) {
        //   年（1月）   季度（4，7，10，1）   月份 （每月)
        List<Integer> quarterStart = Arrays.asList(1, 4, 7, 10);
        LocalDateTime now = LocalDateTime.now();
        int currentMonth = now.getMonthValue();
        if (
                (clearType.equals(IntegralCleanTypeEnum.YEAR.getCode()) && currentMonth == 1) ||
                        (clearType.equals(IntegralCleanTypeEnum.QUARTER.getCode()) && quarterStart.contains(currentMonth)) ||
                        (clearType.equals(IntegralCleanTypeEnum.MONTH.getCode()))
        ) {
            checkReduceUserIntegral(clearType);
        }
    }

    @SneakyThrows
    private void checkReduceUserIntegral(Integer clearType) {
        List<IntegralDetail> details = this.selectUserLastTimeIntegral(clearType, null);
        List<Long> userIds = details.stream()
                .map(IntegralDetail::getUserId).distinct().collect(Collectors.toList());
        for (Long userId : userIds) {
            String lockKey = MessageFormat.format(REDUCE_INTEGRAL_KEY, userId);
            RLock lock = redissonClient.getLock(lockKey);
            boolean tryLock = lock.tryLock(30, 30, TimeUnit.SECONDS);
            if (tryLock) {
                try {
                    this.reduceUserIntegral(clearType, userId);
                } catch (Exception e) {
                    throw ServiceExceptionUtil.exception(ErrorCode.SYSTEM_BUSY);
                } finally {
                    lock.unlock();
                }
            }
        }
    }

    // 查询用户上次时间段的剩余积分
    private List<IntegralDetail> selectUserLastTimeIntegral(Integer clearType, Long userId) {
        return integralDetailMapper.selectUserLastTimeIntegral(clearType, userId);
    }

    /**
     * 清理用户的积分
     */
    private void reduceUserIntegral(Integer clearType, Long userId) {
        List<IntegralDetail> userIntegralDetails = this.selectUserLastTimeIntegral(clearType, userId);
        List<String> ids = new ArrayList<>();
        int outTimeIntegralTotal = 0;
        for (IntegralDetail userIntegralDetail : userIntegralDetails) {
            Integer totalIntegral = userIntegralDetail.getTotalIntegral();
            Integer remainIntegral = userIntegralDetail.getRemainIntegral();
            if (!totalIntegral.equals(remainIntegral)) {
                //还有剩余积分
                outTimeIntegralTotal += remainIntegral;
            }
            Long id = userIntegralDetail.getId();
            IntegralDetail build = IntegralDetail.builder()
                    .id(id)
                    .status(IntegralDetailStatusEnum.OUT_TIME.getCode())
                    .build();
            this.updateIntegralDetailById(build);
            ids.add(id.toString());
        }
        this.insertBill(userId, String.join(",", ids), -outTimeIntegralTotal, IntegralBillTypeEnum.OUT);
    }
}
